package com.alpha.processor.processor.anno_processor;

import com.alpha.binder.anno.processor.DataProcessor;
import com.alpha.binder.anno.provider.Provider;
import com.alpha.binder.anno.view.View;
import com.alpha.processor.processor.tools.ProcessorTool;
import com.alpha.processor.creators.interface_file.ProviderInterfaceFileCreator;
import com.alpha.processor.processor.tools.AnnoProcessorToolException;
import com.alpha.processor.processor.anno_processor.base.AnnoProcessor;
import com.alpha.processor.util.ParseTypeUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;

public class ProviderAnnoProcessor extends AnnoProcessor {

    private final Map<String, ProviderInterfaceFileCreator> interfaceCreateMap = new HashMap<>();

    private final TypeElement viewElement;

    private final TypeElement processorElement;

    public ProviderAnnoProcessor(Messager messager, Elements elementUtils, ProcessorTool logger,
                                 InterfaceAnnoProcessor interfaceAnnoProcessor) {
        super(messager, elementUtils, logger);
        this.viewElement = interfaceAnnoProcessor.getViewElement();
        this.processorElement = interfaceAnnoProcessor.getProcessorElement();
    }

    @Override
    public void processAnno(RoundEnvironment roundEnvironment) {
        Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(Provider.class);
        for (Element element : elementSet) {
            Provider provider = element.getAnnotation(Provider.class);

            TypeElement classElement = (TypeElement) element;
            String className = classElement.getQualifiedName().toString();

            TypeElement viewClassElement = getElementFromType(ParseTypeUtil.fromProviderView(provider));
            if (viewClassElement == null){
                throw new AnnoProcessorToolException("\nError: Can Not Find Provider.view() Of --- " + className);
            }
            TypeElement processorClassElement = getElementFromType(ParseTypeUtil.fromProviderProcessor(provider));
            if (processorClassElement == null){
                throw new AnnoProcessorToolException("\nError: Can Not Find Provider.processor() Of --- " + className);
            }

            String key = viewClassElement.getQualifiedName().toString();

            ProviderInterfaceFileCreator fileCreator = interfaceCreateMap.get(key);
            if (fileCreator == null){
                fileCreator = new ProviderInterfaceFileCreator(logger,elementUtils,classElement);

                VariableElement viewParamElement = findViewAnnoElement(processorClassElement);
                if (viewParamElement == null){
                    throw new AnnoProcessorToolException("\nError: Can Not Find @View Param In --- " + processorClassElement.getQualifiedName());
                }
                VariableElement processorParamElement = findProcessorAnnoElement(viewClassElement);
                if (processorParamElement == null){
                    throw new AnnoProcessorToolException("\nError: Can Not Find @Processor Param In --- " + viewClassElement.getQualifiedName());
                }

                fileCreator.getCreateModel().updateClassElement(viewClassElement,processorClassElement);
                fileCreator.getCreateModel().updateParamElement(viewParamElement,processorParamElement);

                interfaceCreateMap.put(key,fileCreator);
            }
        }
    }

    private VariableElement findViewAnnoElement(TypeElement typeElement){
        List<? extends Element> elementList = typeElement.getEnclosedElements();
        for (Element element:elementList){
            if (element.getAnnotation(View.class) != null){
                TypeMirror type = element.asType();
                TypeElement paramTypeElement = getElementFromType(type);

                if (paramTypeElement.getKind() != ElementKind.INTERFACE){
                    String typeName = type.toString();
                    throw new AnnoProcessorToolException("\n\tError: " + typeName + " Is Not A Interface;\n\tFrom " + typeElement.getQualifiedName() + " " + element.getSimpleName());
                }
                String targetElementName = viewElement.getQualifiedName().toString();
                if (findTargetParentElement(paramTypeElement,targetElementName) == null){
                    String paramName = element.getSimpleName().toString();
                    String typeName = element.asType().toString();
                    throw new AnnoProcessorToolException("\n\tError: " + typeName + " Is Not Sub Type Of <" + targetElementName + ">;\n\tFrom " + typeElement.getQualifiedName() + " " + paramName);
                }
                return (VariableElement) element;
            }
        }
        return null;
    }

    private VariableElement findProcessorAnnoElement(TypeElement typeElement){
        List<? extends Element> elementList = typeElement.getEnclosedElements();
        for (Element element:elementList){
            if (element.getAnnotation(DataProcessor.class) != null){
                TypeMirror type = element.asType();
                TypeElement paramTypeElement = getElementFromType(element.asType());

                if (paramTypeElement.getKind() != ElementKind.INTERFACE){
                    String typeName = type.toString();
                    throw new AnnoProcessorToolException("\n\tError: " + typeName + " Is Not A Interface;\n\tFrom " + typeElement.getQualifiedName() + " " + element.getSimpleName());
                }
                String targetElementName = processorElement.getQualifiedName().toString();
                if (findTargetParentElement(paramTypeElement,targetElementName) == null){
                    String paramName = element.getSimpleName().toString();
                    String typeName = element.asType().toString();
                    throw new AnnoProcessorToolException("\n\tError: " + typeName + " Is Not Sub Type Of <" + targetElementName + ">;\n\tFrom " + typeElement.getQualifiedName() + " " + paramName);
                }
                return (VariableElement) element;
            }
        }
        return null;
    }

    public Map<String, ProviderInterfaceFileCreator> getInterfaceCreateMap() {
        return interfaceCreateMap;
    }
}
